﻿using System;
using System.Collections.Generic;
using System.Text;

using SpotItComponents;

using Vitruvian.Distribution;
using Vitruvian.Distribution.SyncPatterns;
using Vitruvian.Serialization;
using Vitruvian.Serialization.Xml;
using Vitruvian.Services;

using Vitruvian.Logging;
using Vitruvian.Testing;

namespace SpotItServerAPI
{
    [ContainsTests("API.CategoryManager")]
    [DistributionInfo]
    [OptimisticSerialization]
    public class CategoryManager : DistributedManager
    {
        #region Private Data Member
        private SymbolManager symbolManager = null;
        private List<Category> categories = new List<Category>();
        private string categoriesFile = "Categories.xml";
        #endregion

        #region Constructors
        public CategoryManager() : base("Category Manager")
        {
            logger = Logger.GetLogger(typeof(CategoryManager));
        }
        #endregion

        #region Public Properties and Methods
        public string CategoriesFile
        {
            get { return categoriesFile; }
            set { categoriesFile = value; }
        }

        [SyncPattern("RPC")]
        public virtual OperationResult Add(Session currentSession, Category newCategory)
        {
            OperationMethod mth = new OperationMethod(InternalAdd);
            return DoOperation(currentSession, mth, SecurityLevel.Designer, newCategory);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult Remove(Session currentSession, Category existingCategory)
        {
            OperationMethod mth = new OperationMethod(InternalRemove);
            return DoOperation(currentSession, mth, SecurityLevel.Designer, existingCategory);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult Lookup(Session currentSession, string labelFilter, Category parentCategoryFilter)
        {
            OperationMethod mth = new OperationMethod(InternalLookup);
            return DoOperation(currentSession, mth, SecurityLevel.Designer, labelFilter, parentCategoryFilter);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult RemoveAllCategories(Session currentSession)
        {
            OperationMethod mth = new OperationMethod(InternalRemoveAll);
            return DoOperation(currentSession, mth, SecurityLevel.Designer);
        }

        #endregion

        #region Internal Methods for DoOperation
        protected override OperationResult PreCondition()
        {
            OperationResult result = new OperationResult();

            symbolManager = ServiceRegistry.GetPreferredService<SymbolManager>();
            if (symbolManager == null)
                result.ErrorMessage = "Cannot locate a Symbol Manager -- please restart server process";

            return result;
        }

        private OperationResult InternalAdd(object[] opParams)
        {
            OperationResult result =  new OperationResult();

            Category newCategory = opParams[1] as Category;
            if (!categories.Contains(newCategory))
            {
                categories.Add(newCategory);
                Save();
            }
            else
                result.ErrorMessage = "Specified Category already exists";

            return result;
        }

        private OperationResult InternalRemove(object[] opParams)
        {
            OperationResult result = new OperationResult();

            Session currentSession = opParams[0] as Session;
            Category existingCategory = opParams[1] as Category;
            if (categories.Contains(existingCategory))
            {
                // Re-link each child of this category to the parent of this category
                foreach (Category c in categories)
                {
                    if (existingCategory.Equals(c.Parent))
                        c.Parent = existingCategory.Parent;
                }

                // Remove category from Symbols and Desks
                symbolManager.RemoveCategoryFromAllSymbolSets(currentSession, existingCategory);

                // TODO: Remove category from all decks

                // Remove the category from the list of know categories
                categories.Remove(existingCategory);
                Save();
            }
            else
                result.ErrorMessage = "Cannot find the specified Category";


            return result;
        }

        private OperationResult InternalLookup(object[] opParams)
        {
            OperationResult result = new OperationResult();
            List<Category> foundList = new List<Category>();

            string labelFilter = (string) opParams[1];
            Category parentCategoryFilter = opParams[2] as Category;

            foreach (Category c in categories)
            {
                if (
                    (string.IsNullOrEmpty(labelFilter) ||
                            (labelFilter.Length <= c.Label.Length &&
                             labelFilter.ToLower() == c.Label.Substring(0, labelFilter.Length).ToLower())) &&
                    (parentCategoryFilter == null || (c.Parent != null && parentCategoryFilter.Equals(c.Parent)))
                   )
                {
                    foundList.Add(c);
                }
            }

            if (foundList.Count > 0)
                result.Data = foundList;
            else
                result.ErrorMessage = "No categories found";

            return result;
        }

        private OperationResult InternalRemoveAll(object[] opParams)
        {
            OperationResult result = new OperationResult();

            Session currentSession = opParams[0] as Session;

            // Remove all categories for all symbol sets and decks
            foreach (Category c in categories)
            {
                logger.DebugFormat("Remove category {0} from all symbol sets", c.Label);

                symbolManager.RemoveCategoryFromAllSymbolSets(currentSession, c);

                // TODO: Remove category from all decks
            }

            logger.Debug("Clear category list");
            categories.Clear();

            return result;
        }

        #endregion

        #region IService Members

        public override void Init()
        {
            Load();
        }

        public override void Cleanup()
        {
            Save();
        }

        #endregion

        #region Private Data Members

        protected override void Load()
        {
            lock (myLock)
            {
                if (!string.IsNullOrEmpty(CategoriesFile))
                    XmlFramework.Deserialize(CategoriesFile, categories);
            }
        }

        protected override void Save()
        {
            lock (myLock)
            {
                if (!string.IsNullOrEmpty(CategoriesFile))
                    XmlFramework.Serialize(CategoriesFile, categories);
            }
        }

        #endregion

        #region Reuseable Test Setup Methods
        public void Test_SetupTestCategories(Session testSession)
        {
            Category nature;
            Category animals;
            Category plants;
            Category trees;

            Test_CategoryManager_Setup();

            OperationResult result = Lookup(testSession, "Nature", null);
            if (!result.Success)
            {
                nature = new Category("Nature");
                result = Add(testSession, nature);
                Assert.IsTrue(result.Success);
            }
            else
                nature = result.Data as Category;

            result = Lookup(testSession, "Animals", nature);
            if (!result.Success)
            {
                animals = new Category("Animals", nature);
                result = Add(testSession, animals);
                Assert.IsTrue(result.Success);
            }
            else
                animals = result.Data as Category;

            result = Lookup(testSession, "Plants", nature);
            if (!result.Success)
            {
                plants = new Category("Plants", nature);
                result = Add(testSession, plants);
                Assert.IsTrue(result.Success);
            }
            else
                plants = result.Data as Category;

            result = Lookup(testSession, "Trees", nature);
            if (!result.Success)
            {
                trees = new Category("Trees", plants);
                result = Add(testSession, trees);
                Assert.IsTrue(result.Success);
            }
            else
                trees = result.Data as Category;

        }
        #endregion

        #region Unit Test Cases

#if (DEBUG)
        private Session testSession = null;

        [OneTimeSetup]
        private void Test_CategoryManager_Setup()
        {

            sessionManager = ServiceRegistry.GetPreferredService<SessionManager>();
            if (sessionManager == null)
            {
                sessionManager = new SessionManager();
                sessionManager.Init();
                ServiceRegistry.Add(sessionManager);
            }

            symbolManager = ServiceRegistry.GetPreferredService<SymbolManager>();
            if (symbolManager == null)
            {
                symbolManager = new SymbolManager();
                symbolManager.SymbolSetsDirectory = "SymbolSets";
                symbolManager.Init();
                ServiceRegistry.Add(symbolManager);
            }

            UserAccount tmpAccount = new UserAccount("tmp", "tmp", "Temporary", "");
            tmpAccount.IsAdmin = true;
            tmpAccount.IsDesigner = true;
            testSession = sessionManager.CreateSession(tmpAccount);
        }

        [Test(1)]
        private void Test_CategoryManager_Constructor_And_Lookup()
        {
            CategoryManager categoryManager = new CategoryManager();
            OperationResult result;

            // Add a few categories, using the admin session
            categoryManager.Test_SetupTestCategories(testSession);

            // Get a list of all categories
            result = categoryManager.Lookup(testSession, null, null);
            Assert.IsTrue(result.Success);
            List<Category> categories = result.Data as List<Category>;
            Assert.IsNotNull(categories);
            Assert.IsEqual(4, categories.Count);

            // Get a list of categories beginning with a "pl"
            result = categoryManager.Lookup(testSession, "pl", null);
            Assert.IsTrue(result.Success);
            categories = result.Data as List<Category>;
            Assert.IsNotNull(categories);
            Assert.IsEqual(1, categories.Count);
            Assert.IsEqual("Plants", categories[0].Label);

            // Get a list of categories beginning with a "abc"
            result = categoryManager.Lookup(testSession, "abc", null);
            Assert.IsFalse(result.Success);

            // Lookup the Nature category
            result = categoryManager.Lookup(testSession, "Nature", null);
            Assert.IsTrue(result.Success);
            categories = result.Data as List<Category>;
            Assert.IsNotNull(categories);
            Assert.IsEqual(1, categories.Count);
            Assert.IsEqual("Nature", categories[0].Label);
            Category natureCategory = categories[0] as Category;

            // Get a list of categories in the Nature category
            result = categoryManager.Lookup(testSession, null, natureCategory);
            Assert.IsTrue(result.Success);
            categories = result.Data as List<Category>;
            Assert.IsNotNull(categories);
            Assert.IsEqual(2, categories.Count);
            Assert.IsEqual("Animals", categories[0].Label);
            Assert.IsEqual("Plants", categories[1].Label);
        }

        [Test(2)]
        private void Test_CategoryManager_Remove_And_RemoveAll()
        {
            CategoryManager categoryManager = new CategoryManager();

            // Add a few categories, using the admin session
            Category c1 = new Category("Nature");
            Category c2 = new Category("Animals", c1);
            Category c2a = new Category("Dogs", c2);
            Category c2b = new Category("Cats", c2);
            Category c3 = new Category("PlantS", c1);
            Category c3a = new Category("Trees", c3);
            Category c3b = new Category("Flowers", c3);
            Category c3c = new Category("Bushes", c3);

            Assert.IsTrue(categoryManager.Add(testSession, c1).Success);
            Assert.IsTrue(categoryManager.Add(testSession, c2).Success);
            Assert.IsTrue(categoryManager.Add(testSession, c2a).Success);
            Assert.IsTrue(categoryManager.Add(testSession, c2b).Success);
            Assert.IsTrue(categoryManager.Add(testSession, c3).Success);
            Assert.IsTrue(categoryManager.Add(testSession, c3a).Success);
            Assert.IsTrue(categoryManager.Add(testSession, c3b).Success);
            Assert.IsTrue(categoryManager.Add(testSession, c3c).Success);

            // Get a list of all categories
            OperationResult result = categoryManager.Lookup(testSession, null, null);
            Assert.IsTrue(result.Success);
            List<Category> categories = result.Data as List<Category>;
            Assert.IsNotNull(categories);
            Assert.IsEqual(8, categories.Count);

            result = categoryManager.Remove(testSession, c3);
            Assert.IsTrue(result.Success);

            result = categoryManager.Lookup(testSession, null, null);
            Assert.IsTrue(result.Success);
            categories = result.Data as List<Category>;
            Assert.IsNotNull(categories);
            Assert.IsEqual(7, categories.Count);
            Assert.IsTrue(categories.Contains(c1));
            Assert.IsTrue(categories.Contains(c2));
            Assert.IsTrue(categories.Contains(c2a));
            Assert.IsTrue(categories.Contains(c2b));
            Assert.IsTrue(categories.Contains(c3a));
            Assert.IsTrue(categories.Contains(c3b));
            Assert.IsTrue(categories.Contains(c3c));

            // TODO: Check that 3a, 3b, 3c are now child of nature

            result = categoryManager.RemoveAllCategories(testSession);
            Assert.IsTrue(result.Success);

            result = categoryManager.Lookup(testSession, null, null);
            Assert.IsFalse(result.Success);
        }

#endif
        #endregion

    }
}
